VONMISES_FISHER
Overview
The VONMISES_FISHER function computes the probability density function (PDF), log-PDF, entropy, or draws random samples from a von Mises-Fisher distribution on the unit hypersphere. This distribution is fundamental in directional statistics for modeling data on spherical surfaces, such as unit vectors in \mathbb{R}^p.
Named after Richard von Mises and Ronald Fisher, the von Mises-Fisher distribution serves as the spherical analogue of the normal distribution. While the normal distribution describes variability along a line, the von Mises-Fisher distribution describes variability around a central direction on a hypersphere. When p = 2, it reduces to the von Mises distribution on the circle; for p = 3, it is commonly called the Fisher distribution.
The probability density function for a unit vector \mathbf{x} is:
f(\mathbf{x}) = C_p(\kappa) \exp(\kappa \boldsymbol{\mu}^T \mathbf{x})
where \boldsymbol{\mu} is the mean direction (a unit vector), \kappa > 0 is the concentration parameter, p is the dimensionality, and the normalization constant is:
C_p(\kappa) = \frac{\kappa^{p/2-1}}{(2\pi)^{p/2} I_{p/2-1}(\kappa)}
Here, I_\nu denotes the modified Bessel function of the first kind at order \nu.
The concentration parameter \kappa controls how tightly the distribution clusters around the mean direction \boldsymbol{\mu}. Higher values of \kappa produce distributions concentrated near \boldsymbol{\mu}, while \kappa = 0 yields a uniform distribution on the hypersphere. The reciprocal 1/\kappa is analogous to variance in normal distributions.
This implementation uses SciPy’s vonmises_fisher from the scipy.stats module. The function supports four computation methods: pdf for probability density, logpdf for log-density (useful for numerical stability), entropy for differential entropy, and rvs for generating random samples.
Applications of the von Mises-Fisher distribution include analyzing geological orientations, modeling text embeddings in machine learning (where cosine similarity between unit vectors is used), clustering on hyperspheres, and modeling molecular orientations in bioinformatics. For further background, see the von Mises-Fisher distribution on Wikipedia and the textbook Directional Statistics by Mardia and Jupp.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=VONMISES_FISHER(x, mu, kappa, vmf_method, size)
x(list[list], optional, default: null): 2D array of points (each row is a unit vector) at which to evaluate the function. Required for ‘pdf’ and ‘logpdf’.mu(list[list], optional, default: null): Mean direction of the distribution as a column vector. Must be a unit vector (norm = 1).kappa(float, optional, default: null): Concentration parameter. Must be positive.vmf_method(str, optional, default: “pdf”): Computation method to use.size(int, optional, default: null): Number of random samples to draw when using ‘rvs’ method.
Returns (list[list]): 2D list of results, or error message string.
Examples
Example 1: PDF at point aligned with mean direction
Inputs:
| x | mu | kappa | vmf_method | ||
|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 2 | |
| 0 | |||||
| 0 |
Excel formula:
=VONMISES_FISHER({1,0,0}, {1;0;0}, 2, "pdf")
Expected output:
| Result |
|---|
| 0.3242 |
Example 2: Log-PDF at point aligned with mean direction
Inputs:
| x | mu | kappa | vmf_method | ||
|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 2 | logpdf |
| 0 | |||||
| 0 |
Excel formula:
=VONMISES_FISHER({1,0,0}, {1;0;0}, 2, "logpdf")
Expected output:
| Result |
|---|
| -1.126 |
Example 3: Entropy of distribution with kappa=2
Inputs:
| mu | kappa | vmf_method |
|---|---|---|
| 1 | 2 | entropy |
| 0 | ||
| 0 |
Excel formula:
=VONMISES_FISHER({1;0;0}, 2, "entropy")
Expected output:
| Result |
|---|
| 2.052 |
Example 4: PDF at multiple points on unit sphere
Inputs:
| x | mu | kappa | vmf_method | ||
|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 2 | |
| 0 | 1 | 0 | 0 | ||
| 0 |
Excel formula:
=VONMISES_FISHER({1,0,0;0,1,0}, {1;0;0}, 2, "pdf")
Expected output:
| Result |
|---|
| 0.3242 |
| 0.0439 |
Python Code
from scipy.stats import vonmises_fisher as scipy_vonmises_fisher
import math
def vonmises_fisher(x=None, mu=None, kappa=None, vmf_method='pdf', size=None):
"""
Computes the PDF, log-PDF, entropy, or draws random samples from a von Mises-Fisher distribution on the unit hypersphere.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.vonmises_fisher.html
This example function is provided as-is without any representation of accuracy.
Args:
x (list[list], optional): 2D array of points (each row is a unit vector) at which to evaluate the function. Required for 'pdf' and 'logpdf'. Default is None.
mu (list[list], optional): Mean direction of the distribution as a column vector. Must be a unit vector (norm = 1). Default is None.
kappa (float, optional): Concentration parameter. Must be positive. Default is None.
vmf_method (str, optional): Computation method to use. Valid options: PDF, Log-PDF, Entropy, RVS. Default is 'pdf'.
size (int, optional): Number of random samples to draw when using 'rvs' method. Default is None.
Returns:
list[list]: 2D list of results, or error message string.
"""
def to2d(val):
return [[val]] if not isinstance(val, list) else val
def to_native_float(val):
if isinstance(val, (float, int)):
return float(val)
if hasattr(val, 'item'):
return float(val.item())
return val
# Validate vmf_method
valid_methods = {'pdf', 'logpdf', 'entropy', 'rvs'}
if vmf_method not in valid_methods:
return f"Invalid vmf_method: {vmf_method}. Must be one of {sorted(valid_methods)}."
# Validate mu
if mu is None:
return "Invalid input: mu must be a 2D list (column vector) with shape (d, 1)."
mu = to2d(mu)
if not isinstance(mu, list) or not all(isinstance(row, list) and len(row) == 1 for row in mu):
return "Invalid input: mu must be a 2D list (column vector) with shape (d, 1)."
try:
mu_vec = [float(row[0]) for row in mu]
except Exception:
return "Invalid input: mu must contain numeric values."
# Check if mu is a unit vector
norm_mu = math.sqrt(sum(v**2 for v in mu_vec))
if not math.isclose(norm_mu, 1.0, rel_tol=1e-5):
return "Invalid input: mu must be a unit vector (norm = 1)."
# Validate kappa
if kappa is None:
return "Invalid input: kappa (concentration) must be specified."
try:
kappa_val = float(kappa)
except Exception:
return "Invalid input: kappa must be a float."
if kappa_val <= 0:
return "Invalid input: kappa must be positive."
dist = scipy_vonmises_fisher(mu=mu_vec, kappa=kappa_val)
if vmf_method == 'entropy':
try:
ent = dist.entropy()
except Exception as e:
return f"scipy_vonmises_fisher entropy error: {e}"
return [[to_native_float(ent)]]
if vmf_method == 'rvs':
if size is None:
size_val = 1
else:
try:
size_val = int(size)
except Exception:
return "Invalid input: size must be an integer."
if size_val <= 0:
return "Invalid input: size must be positive."
try:
samples = dist.rvs(size=size_val)
except Exception as e:
return f"scipy_vonmises_fisher rvs error: {e}"
if samples.ndim == 1:
return [[to_native_float(v) for v in samples.tolist()]]
elif samples.ndim == 2:
return [[to_native_float(v) for v in row] for row in samples.tolist()]
else:
return "Invalid output shape from rvs."
# For pdf/logpdf, x is required
if x is not None:
x = to2d(x)
if x is None or not isinstance(x, list) or not all(isinstance(row, list) for row in x):
return "Invalid input: x must be a 2D list of points."
# Check each row in x matches mu dimension
if any(len(row) != len(mu_vec) for row in x):
return "Invalid input: each row in x must have the same dimension as mu."
results = []
for row in x:
try:
point = [float(val) for val in row]
except Exception:
results.append([""])
continue
try:
if vmf_method == 'pdf':
val = dist.pdf(point)
else:
val = dist.logpdf(point)
# Disallow nan/inf
val_native = to_native_float(val)
if isinstance(val_native, float) and (math.isnan(val_native) or math.isinf(val_native)):
results.append([""])
else:
results.append([val_native])
except Exception:
results.append([""])
return results